<!DOCTYPE HTML>
<html lang="en">
<head>
  <meta charset="utf8">
  <title>Test for JavaScript terminal functionality</title>
  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
  <script type="text/javascript;version=1.8" src="common.js"></script>
  <!-- Any copyright is dedicated to the Public Domain.
     - http://creativecommons.org/publicdomain/zero/1.0/ -->
</head>
<body>
<p>Test for JavaScript terminal functionality</p>

<iframe id="content-iframe" src="http://example.com/chrome/devtools/shared/webconsole/test/sandboxed_iframe.html"></iframe>

<script class="testbody" type="text/javascript;version=1.8">
SimpleTest.waitForExplicitFinish();

let gState;

let {MAX_AUTOCOMPLETE_ATTEMPTS,MAX_AUTOCOMPLETIONS} = require("devtools/shared/webconsole/js-property-provider");

// This test runs all of its assertions twice - once with
// evaluateJS and once with evaluateJSAsync.
let evaluatingSync = true;
function evaluateJS(input, options = {}) {
  return new Promise((resolve, reject) => {
    if (evaluatingSync) {
      gState.client.evaluateJS(input, resolve, options);
    } else {
      gState.client.evaluateJSAsync(input, resolve, options);
    }
  });
}

function startTest()
{
  removeEventListener("load", startTest);

  attachConsoleToTab(["PageError"], onAttach);
}

function onAttach(aState, aResponse)
{
  top.foobarObject = Object.create(null);
  top.foobarObject.foo = 1;
  top.foobarObject.foobar = 2;
  top.foobarObject.foobaz = 3;
  top.foobarObject.omg = 4;
  top.foobarObject.omgfoo = 5;
  top.foobarObject.strfoo = "foobarz";
  top.foobarObject.omgstr = "foobarz" +
    (new Array(DebuggerServer.LONG_STRING_LENGTH * 2)).join("abb");

  top.largeObject1 = Object.create(null);
  for (let i = 0; i < MAX_AUTOCOMPLETE_ATTEMPTS + 1; i++) {
    top.largeObject1['a' + i] = i;
  }

  top.largeObject2 = Object.create(null);
  for (let i = 0; i < MAX_AUTOCOMPLETIONS * 2; i++) {
    top.largeObject2['a' + i] = i;
  }

  gState = aState;

  let tests = [doSimpleEval, doWindowEval, doEvalWithException,
               doEvalWithHelper, doEvalString, doEvalLongString,
               doEvalWithBinding, doEvalWithBindingFrame,
               forceLexicalInit].map(t => {
                 return Task.async(t);
               });

  runTests(tests, testEnd);
}

function* doSimpleEval() {
  info("test eval '2+2'");
  let response = yield evaluateJS("2+2");
  checkObject(response, {
    from: gState.actor,
    input: "2+2",
    result: 4,
  });

  ok(!response.exception, "no eval exception");
  ok(!response.helperResult, "no helper result");

  nextTest();
}

function* doWindowEval() {
  info("test eval 'document'");
  let response = yield evaluateJS("document");
  checkObject(response, {
    from: gState.actor,
    input: "document",
    result: {
      type: "object",
      class: "XULDocument",
      actor: /[a-z]/,
    },
  });

  ok(!response.exception, "no eval exception");
  ok(!response.helperResult, "no helper result");

  nextTest();
}

function* doEvalWithException() {
  info("test eval with exception");
  let response = yield evaluateJS("window.doTheImpossible()");
  checkObject(response, {
    from: gState.actor,
    input: "window.doTheImpossible()",
    result: {
      type: "undefined",
    },
    exceptionMessage: /doTheImpossible/,
  });

  ok(response.exception, "js eval exception");
  ok(!response.helperResult, "no helper result");

  nextTest();
}

function* doEvalWithHelper() {
  info("test eval with helper");
  let response = yield evaluateJS("clear()");
  checkObject(response, {
    from: gState.actor,
    input: "clear()",
    result: {
      type: "undefined",
    },
    helperResult: { type: "clearOutput" },
  });

  ok(!response.exception, "no eval exception");

  nextTest();
}

function* doEvalString() {
  let response = yield evaluateJS("window.foobarObject.strfoo");
  checkObject(response, {
    from: gState.actor,
    input: "window.foobarObject.strfoo",
    result: "foobarz",
  });

  nextTest();
}

function* doEvalLongString() {
  let response = yield evaluateJS("window.foobarObject.omgstr");
  let str = top.foobarObject.omgstr;
  let initial = str.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH);

  checkObject(response, {
    from: gState.actor,
    input: "window.foobarObject.omgstr",
    result: {
      type: "longString",
      initial: initial,
      length: str.length,
    },
  });

  nextTest();
}

function* doEvalWithBinding() {
  let response = yield evaluateJS("document;");
  let documentActor = response.result.actor;

  info("running a command with _self as document using bindObjectActor");
  let bindObjectSame = yield evaluateJS("_self === document", {
    bindObjectActor: documentActor
  });
  checkObject(bindObjectSame, {
    result: true
  });

  info("running a command with _self as document using selectedObjectActor");
  let selectedObjectSame = yield evaluateJS("_self === document", {
    selectedObjectActor: documentActor
  });
  checkObject(selectedObjectSame, {
    result: true
  });

  nextTest();
}

function* doEvalWithBindingFrame() {
  let frameWin = top.document.querySelector("iframe").contentWindow;
  frameWin.fooFrame = { bar: 1 };

  let response = yield evaluateJS(
    "document.querySelector('iframe').contentWindow.fooFrame"
  );
  let iframeObjectActor = response.result.actor;
  ok(iframeObjectActor, "There is an actor associated with the response");

  let bindObjectGlobal = yield evaluateJS("this.temp0 = _self;", {
    bindObjectActor: iframeObjectActor
  });
  ok(!top.temp0,
    "Global doesn't match the top global with bindObjectActor");
  ok(frameWin.temp0 && frameWin.temp0.bar === 1,
    "Global matches the object's global with bindObjectActor");

  let selectedObjectGlobal = yield evaluateJS("this.temp1 = _self;", {
    selectedObjectActor: iframeObjectActor
  });
  ok(top.temp1 && top.temp1.bar === 1,
    "Global matches the top global with bindObjectActor");
  ok(!frameWin.temp1,
    "Global doesn't match the object's global with bindObjectActor");

  nextTest()
}

function* forceLexicalInit() {
  info("test that failed let/const bindings are initialized to undefined");

  const testData = [
    {
        stmt: "let foopie = wubbalubadubdub",
        vars: ["foopie"]
    },
    {
        stmt: "let {z, w={n}=null} = {}",
        vars: ["z", "w"]
    },
    {
        stmt: "let [a, b, c] = null",
        vars: ["a", "b", "c"]
    },
    {
        stmt: "const nein1 = rofl, nein2 = copter",
        vars: ["nein1", "nein2"]
    },
    {
        stmt: "const {ha} = null",
        vars: ["ha"]
    },
    {
        stmt: "const [haw=[lame]=null] = []",
        vars: ["haw"]
    },
    {
        stmt: "const [rawr, wat=[lame]=null] = []",
        vars: ["rawr", "haw"]
    },
    {
        stmt: "let {zzz: xyz=99, zwz: wb} = nexistepas()",
        vars: ["xyz", "wb"]
    },
    {
        stmt: "let {c3pdoh=101} = null",
        vars: ["c3pdoh"]
    }
  ];

  for (let data of testData) {
      let response = yield evaluateJS(data.stmt);
      checkObject(response, {
        from: gState.actor,
        input: data.stmt,
        result: undefined,
      });
      ok(response.exception, "expected exception");
      for (let varName of data.vars) {
          let response2 = yield evaluateJS(varName);
          checkObject(response2, {
            from: gState.actor,
            input: varName,
            result: undefined,
          });
          ok(!response2.exception, "unexpected exception");
      }
  }

  nextTest();
}

function testEnd()
{
  // If this is the first run, reload the page and do it again.
  // Otherwise, end the test.
  closeDebugger(gState, function() {
    gState = null;
    if (evaluatingSync) {
      evaluatingSync = false;
      startTest();
    } else {
      SimpleTest.finish();
    }
  });
}

addEventListener("load", startTest);
</script>
</body>
</html>
